#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <simpio.h>
#include <genlib.h>
#include <strlib.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>


#define DEFAULT_BUFFLEN 512
#define MAX_THREADS 5
#define NOT_FOUND 404
#define OK 200
#define NOT_MODIFIED 304
#define RET_FAILED -1
#define RET_SUCCESS 1
#define RETUR 13
#define SERVICE_ON 1
#define SERVICE_OFF 2
#define STATUS 3
#define DOWNLOAD 4
#define END 5


typedef struct{
    string portNumber;
    string filePath;
    string logPath;
    string telnetPort;
    string telnetPassword;
} configT;

typedef struct{
	string userIP;
	char timeStr[80];
	char GMT[10];
	char cmdHTTP[80]; //Get / Conditional GET
	char filenameHTTP[80];
	char protocolHTTP[80];
	int httpAnswer; //OK, Not modified, Error
	int bitsDownloaded;
}*userInfoT;

bool serverOnlineOrNot;

DWORD WINAPI handleThreads(LPVOID threadData);

DWORD WINAPI handleTelnetThread(LPVOID threadData);
/*
 Skapar en listenSocket
*/
SOCKET createListenSocket(string portNr);

/*
 Skickar en fil till användaren. Returnar totala mängden skickade bits. Tror jag iaf : )
*/
int sendFile(char *fileRequest, SOCKET clientSocket, string header);
/*
 check kollar efter conditional get
*/
void checkGet(char *httpReq, userInfoT userInfo, configT configFile);
/*
 Eftersom jag inte orkade göra det där med difftime så kollar jag bara strängarna med varandra
 och om de inte matchar så är den fan uppdaterad då. Konstigt annars. Men vi måste räkna ut diff tiden
*/
int checkFileTime(char *filename, char *modTimeSince);

int saveUserInfo(userInfoT userInfo, string userFile);

int initWSA();

bool serverStatus();

int readConfigFile(configT *configFile);

string contentType(string mime);

int checkFileExist(userInfoT userInfo, configT configFile);

string createHeader(userInfoT userInfo, string mimeType);

int monthToMonth(char *month);

int getTime(userInfoT userInfo);

int recvMessageTelnet(SOCKET clientSocket, char *incMessage);

int telnetRequest(string request);

int handelRequests(int request, SOCKET clientSocket, string logFile);

int main(){
	int err;
	//socketInfoT sockInfo;
	SOCKET clientSocket = INVALID_SOCKET;
	SOCKET listenSocket = INVALID_SOCKET;
	SOCKET telnetSocket = INVALID_SOCKET;
	HANDLE telnetThread;
	configT configFile;
	int clientAddrLen;
    struct sockaddr clientAddr;
	DWORD nThreadID;
	char hostName[100], portName[100];


	//Initierar Winsock och kollar efter error
	err = initWSA();
	if(err < 0){
		printf("Failed to initialize WSA\n");
		return 1;
	}
	//Sätter server till online
	serverOnlineOrNot = TRUE;
	//Initierar config filen
	if(readConfigFile(&configFile) < 0){
		printf("Reading file failed\n");
		WSACleanup();
		return 0;
	}

	//Telnet

	telnetSocket = createListenSocket(configFile.telnetPort);
	telnetThread = CreateThread(NULL, 0, handleTelnetThread, (void *)telnetSocket, 0, NULL);
	if(telnetThread == NULL){
		printf("Failed in creating telnetThread\n");
	}
	listenSocket = createListenSocket(configFile.portNumber);
	clientAddrLen = sizeof(clientAddr);

	while(TRUE){

		if(serverStatus()){//Kollar om servern är online eller inte

			if(listenSocket == INVALID_SOCKET){ //Om servern har varit nere skapar vi om listensocketen
				listenSocket = createListenSocket(configFile.portNumber);
			}

			clientSocket = accept(listenSocket,&clientAddr,&clientAddrLen);
			if(clientSocket != INVALID_SOCKET) {

				err = getnameinfo(&clientAddr,clientAddrLen,hostName,100,portName,100,NI_NUMERICSERV );
				if(err == 0) {
					printf("Accept incoming from: %s at port %s\n",hostName,portName);
				}else
					printf("getnameinfo failed\n");

				CreateThread(NULL, 0, handleThreads, (void *)clientSocket, 0, &nThreadID);
			}else{
				printf("socket failed with error %d\n", WSAGetLastError());
				closesocket(clientSocket);
				return RET_FAILED;
			}

		}else{
			closesocket(listenSocket);
			listenSocket = INVALID_SOCKET;
		}
	}

	shutdown(listenSocket,3);
    WSACleanup();
	getchar();
	return 0;
}

bool serverStatus(){
	return serverOnlineOrNot;
}

int readConfigFile(configT *configFile){
    FILE* fp;

    if((fp = fopen("config.txt", "r")) == NULL){
            printf("Couldn't open config.txt\n");
            return RET_FAILED;
    }

    configFile->portNumber     = ReadLine(fp);
    configFile->filePath       = ReadLine(fp);
    configFile->logPath        = ReadLine(fp);
    configFile->telnetPort     = ReadLine(fp);
    configFile->telnetPassword = ReadLine(fp);

    fclose(fp);

    return RET_SUCCESS;
}

int initWSA(){
	int err;

	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2,2);
	err = WSAStartup(wVersionRequested, &wsaData);
	if(err != 0){
        return -1;
	}
	return 1;
}

SOCKET createListenSocket(string portNr){
	int iResult;
	SOCKET listenSocket;
	struct addrinfo *info;

	listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(listenSocket == INVALID_SOCKET){
		printf("Socket function failed with error %d\n", WSAGetLastError());
		closesocket(listenSocket);
		return INVALID_SOCKET;
	}

	iResult = getaddrinfo("localhost", portNr, NULL, &info); 
	if(iResult != 0){
		printf("getaddrinfo failed with an error %d\n", WSAGetLastError());
		return INVALID_SOCKET;
	}else while(info->ai_family != AF_INET && info->ai_next != NULL)
		info = info->ai_next;

	//Lyssna på addressen via socketen
	iResult = bind(listenSocket, info->ai_addr, info->ai_addrlen);
	if(iResult != 0){
		printf("Bind failed with error %d\n", WSAGetLastError());
		closesocket(listenSocket);
		return INVALID_SOCKET;
	}

	iResult = listen(listenSocket, SOMAXCONN);
	if(iResult == SOCKET_ERROR){
		printf("Listening failed with an error %d\n", WSAGetLastError());
		closesocket(listenSocket);
		return INVALID_SOCKET;
	}
	printf("Listening socket is listening...\n");

	return listenSocket;
}

int sendFile(char *fileRequest, SOCKET clientSocket, string header){

	FILE *sendFile;
	int iSendResult,freadResult, totalSentBits = 0;
	char sendBuff[DEFAULT_BUFFLEN];

	iSendResult = send(clientSocket,header,strlen(header),0);

	sendFile = fopen(fileRequest,"rb");
	if(sendFile == NULL){
		printf("Error, cant open file: %s\n", fileRequest);
		return -1;
	}
	//Kolla hur stor filen är och om hela inte skickats meddela det på något sätt
	while(!feof(sendFile)){
		freadResult=fread(sendBuff,sizeof(char),DEFAULT_BUFFLEN,sendFile);
		iSendResult = send(clientSocket,sendBuff,freadResult,0);
		if (iSendResult == SOCKET_ERROR) {
            printf("send failed: %d\n", WSAGetLastError());
            return totalSentBits;
        }
		totalSentBits += iSendResult;
	}
	fclose(sendFile);
	return totalSentBits;
}

int checkFileTime(char *filename, char *modTimeSince){
	struct stat buf;
	struct tm timeSince;
	char timeStr[ 100 ] = "", buff[100];
	time_t sinceTime;
	double result;
	char year[10], mon[10], day[10], hour[10], min[10], sec[10], buffer[10];

	sscanf(modTimeSince,"%s %s %s %s %2s%*c%2s%*c%2s", buffer, day, mon, year, hour, min, sec);
	timeSince.tm_year = atoi(year)-1900;
	timeSince.tm_mon = monthToMonth(mon);
	timeSince.tm_mday = atoi(day);
	timeSince.tm_hour = atoi(hour);
	timeSince.tm_min = atoi(min);
	timeSince.tm_sec = atoi(sec);

	if((sinceTime = mktime(&timeSince)) != (time_t)-1){
		/*asctime_s( buff, sizeof(buff), &timeSince);
		printf( "In days the time wilsddafgdsfgl be %s\n", buff );*/
	}else
		return OK; //Om vi inte har en tid att jämföra med så skickar vi OK

	if (!stat(filename, &buf)){
		/*strftime(timeStr, 100, "%d %b %Y %H:%M:%S", localtime( &buf.st_mtime));
		printf("\nLast modified date and time = %s\n", timeStr);*/
	}else{
		return NOT_FOUND; //Hittar inte filen, skicka felmeddelande
	}
	result = difftime(buf.st_mtime, sinceTime);
	//printf("Resultat: %f\n", result);
	if(result > 0)
		return NOT_MODIFIED; //Tiden har inte blivit modifierad
	else
		return OK; //Tiden har blivit modifierad
}

void checkGet(char *httpReq, userInfoT userInfo, configT configFile){

	char *inputHTTP = "GET /index.html HTTP/1.1\nHost: www.someschool.edu\nUser-agent: Mozilla/4.0\nConnection: close\nIf-modified-since: Sun, 14 Feb 2013 18:22:42 GMT\n\n";
    char cmdHTTP[150];
	char filenameHTTP[150];
	char protocolHTTP[150];
	int ok = sscanf(httpReq,"%s %s %s",cmdHTTP,filenameHTTP,protocolHTTP);
	char *next = strstr(httpReq,"\n");
	while(next) {
		char name[150];
		char value[150];
		int ok = sscanf(next+1,"%s",name);
		char *space = strstr(next+1," ");
		next = strstr(next+1,"\n");
		if(space) {
		    memcpy(value,space+1,next-space-1);
		    value[next-space-1]=0;
		}
		if(ok==1) {
			if(strcmp(name,"If-modified-since:")==0) {
				if(strcmp(cmdHTTP,"GET")==0) {
					printf("I got a conditional GET with the date: %s\n",value);
					userInfo->httpAnswer = checkFileTime(Concat(configFile.filePath,userInfo->filenameHTTP),value);
				}
			}
		}
	}
}

string contentType(string mime);

DWORD WINAPI handleThreads(void * threadData){

	//socketInfoT sockInfo = (socketInfoT)threadData;
	SOCKET clientSocket = (SOCKET)threadData;
	userInfoT userInfo;
	configT configFile;
	struct hostent *remoteHost;
	int iResult;
	string indexHTML, header;
	char server_host[100], recvBuff[512];
	string sendMime;
	userInfo = New(userInfoT);
	
	if(readConfigFile(&configFile) < 0){
		printf("Reading file failed\n");
		return RET_FAILED;
	}

	//Hämtar IP adress om clienten Kan göra nya function för detta
	iResult = gethostname(server_host, sizeof(server_host));
	if(iResult == SOCKET_ERROR){
		printf("gethostname failed with and error: %d\n", WSAGetLastError());
		userInfo->userIP = "Could not find Ip adress";
	}else{
		remoteHost=gethostbyname(server_host);
		userInfo->userIP = inet_ntoa (*(struct in_addr *)*remoteHost->h_addr_list);
	}

	//Tar emot data från clienten. om inget har tagits emot så stänges anslutningen ner.
	iResult = recv(clientSocket, recvBuff, 512, 0);
	if(iResult > 0){
	}else{
		printf("Recv failed with an error: %d\n", WSAGetLastError());
		printf("Closing socket...\n");
		closesocket(clientSocket);
		return RET_FAILED;
	}
	recvBuff[iResult] = '\0'; // Måste göras till string annars funkar inte checkGet som Håkan har gjort
	//printf("%s\n", recvBuff);

	userInfo->httpAnswer = OK; //Vi sätter den till OK, om den inte är modified eller inte finns sätts den till det senare
	sscanf(recvBuff,"%s %s %s\n",userInfo->cmdHTTP, userInfo->filenameHTTP, userInfo->protocolHTTP);
	if(strcmp(userInfo->filenameHTTP, "/") == 0){
		indexHTML = "\\index.html";
		sscanf(indexHTML, "%s", userInfo->filenameHTTP);
	}else{
		userInfo->filenameHTTP[0] = '\\';
	}

	//Kollar om filen finns och lägger till korrekt httpAnswer
	iResult = checkFileExist(userInfo,configFile);
	if(iResult < 0){
		//printf("filen finns inte\n");
		userInfo->httpAnswer = NOT_FOUND;
	}else{ //Filen finns
		sendMime = contentType(userInfo->filenameHTTP);
		checkGet(recvBuff, userInfo, configFile);
	}

	//Lägger till tiden
	/*time(&lTime);
	strftime(userInfo->timeStr, 80, "[ %d/%b/%Y:%H:%M:%S %z]\n", localtime( &lTime));*/
	iResult = getTime(userInfo);
	if(iResult > 0)
		printf("time succedded\n");

	//Skapar header
	header = createHeader(userInfo, sendMime);

	//printf("Header utanför createheader: %s\n", header);
	//Om httpAnswer är OK så skickas filen, annars så skickas ingen fil.
	if(userInfo->httpAnswer == OK){
		userInfo->bitsDownloaded = sendFile(Concat(configFile.filePath,userInfo->filenameHTTP), clientSocket, header);
		if(userInfo->bitsDownloaded < 0)
			userInfo->bitsDownloaded = 0;
	}else if(userInfo->httpAnswer == NOT_FOUND){
		string sendMessage = "<html><h1>Error 404 NOT FOUND</h1></html>";
		iResult = send(clientSocket,sendMessage,strlen(sendMessage),0);
		userInfo->bitsDownloaded = iResult;
	}

	iResult = saveUserInfo(userInfo, configFile.logPath);
	if(iResult < 0){
		printf("Failed in storing user information\n");
	}else
		printf("Successfully stored user information\n");

	printf("Closing socket...\n");
	iResult = shutdown(clientSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed with error: %d\n", WSAGetLastError());
        closesocket(clientSocket);
        return 0;
    }
	closesocket(clientSocket);
	return 0;
}

DWORD WINAPI handleTelnetThread(LPVOID threadData){

	SOCKET telnetSocket = (SOCKET)threadData;
	SOCKET clientSocket;
	configT configFile;
	int iResult, passSize;
	bool closeConnection = TRUE;

	char incMessage[100] = "";
	string sendMessage, sendPassMessage;
	sendMessage = "Hello, welcome to the telnet skit\r\n";
	sendPassMessage = "Please enter password:\r\n";

	readConfigFile(&configFile);

	while(TRUE){

		clientSocket = accept(telnetSocket, NULL, NULL);

		if(clientSocket == INVALID_SOCKET){
			printf("Socket invalid\n");
			closesocket(clientSocket);
			return 0;
		}

		printf("En client har anslutit\n");
		//Skicka intro message
		send(clientSocket, sendMessage, strlen(sendMessage), 0);
		send(clientSocket, sendPassMessage, strlen(sendPassMessage), 0);

		//Kollar password
		passSize = strlen(configFile.telnetPassword);
		while(1){
			iResult = recvMessageTelnet(clientSocket, incMessage);
			if(StringEqual(incMessage, configFile.telnetPassword) 
				|| StringEqual(SubString(incMessage, 1, passSize), configFile.telnetPassword))
				break;
		}
		sendPassMessage = "Welcome!\r\n";
		send(clientSocket, sendPassMessage, strlen(sendPassMessage), 0);
	
		while(closeConnection){
			sendMessage = "What would you like to do?\r\n";
			send(clientSocket, sendMessage, strlen(sendMessage), 0);
			sendMessage = "SERVICE ON, SERVICE OFF\r\nSTATUS\r\nDOWNLOAD\r\nEND\r\n\r\n";
			send(clientSocket, sendMessage, strlen(sendMessage), 0);

			recvMessageTelnet(clientSocket, incMessage);
			iResult = telnetRequest(incMessage);
			closeConnection = handelRequests(iResult, clientSocket, configFile.logPath);

		}
		closesocket(clientSocket);
		clientSocket = INVALID_SOCKET;
	}
	return 0;
}

int handelRequests(int request, SOCKET clientSocket, string logFile){
	string message;

	if(request == RET_FAILED)
		return TRUE;
	else if(request == SERVICE_ON){
		printf("Server is on\n");
		serverOnlineOrNot = TRUE;
		return TRUE;
	}
	else if(request == SERVICE_OFF){
		printf("Server is closing\n");
		serverOnlineOrNot = FALSE;
		return TRUE;
	}
	else if(request == STATUS){
		if(serverOnlineOrNot == TRUE){
			message = "Server is running\r\n";
			send(clientSocket, message, strlen(message), 0);
		}else{
			message = "Server is down\r\n";
			send(clientSocket, message, strlen(message), 0);
		}
		return TRUE;
	}
	else if(request == DOWNLOAD){
		char sendBuff[DEFAULT_BUFFLEN];
		FILE *fp;
		int i;
		if((fp = fopen(logFile, "r")) == NULL){
			message = "Failed to open log file\r\n";
			send(clientSocket, message, strlen(message), 0);
			return TRUE;
		}

		while(fp != EOF){
			if(fgets(sendBuff,DEFAULT_BUFFLEN,fp) == NULL)
				break;
			i = strlen(sendBuff);
			sendBuff[i] = '\r';
			sendBuff[i+1] = '\n';
			i++;
			send(clientSocket,sendBuff,i,0);
		}
		fclose(fp);
		return TRUE;
	}
	else if(request == END){
		closesocket(clientSocket);
		return FALSE;
	}
	else
		return TRUE;

}

int telnetRequest(string request) {
	
	if(StringEqual(request, "SERVICE ON") 
			|| StringEqual(SubString(request, 1, strlen("SERVICE ON")), "SERVICE ON"))
			return SERVICE_ON;
	else if(StringEqual(request, "SERVICE OFF") 
			|| StringEqual(SubString(request, 1, strlen("SERVICE OFF")), "SERVICE OFF"))
			return SERVICE_OFF;
	else if(StringEqual(request, "STATUS") 
			|| StringEqual(SubString(request, 1, strlen("STATUS")), "STATUS"))
			return STATUS;
	else if(StringEqual(request, "DOWNLOAD") 
			|| StringEqual(SubString(request, 1, strlen("DOWNLOAD")), "DOWNLOAD"))
			return DOWNLOAD;
	else if(StringEqual(request, "END") 
			|| StringEqual(SubString(request, 1, strlen("END")), "END"))
			return END;
	else
		return RET_FAILED;

}

int recvMessageTelnet(SOCKET clientSocket, char *incMessage){

	int i = 0, rSize = 0;
	char ch;

	while (rSize += recv(clientSocket, &ch, 1, 0)) { //Den här fuckar sig om man stänger av telnet genom X 
		if (ch == (char)RETUR)
			break;

		incMessage[i] = ch;					
		send(clientSocket, (char*)ch, 1, 0);	
		i++;
	}

	incMessage[i+1] = '\0';

	return rSize;
}

string createHeader(userInfoT userInfo, string mimeType){
	
	string header, buffer;
	buffer = IntegerToString(userInfo->httpAnswer);
	header = "";
	header = Concat(header,userInfo->protocolHTTP);
	header = Concat(header, " ");
	header = Concat(header, buffer);
	header = Concat(header, " ");

	if(userInfo->httpAnswer == 404){
		header = Concat(header,"Not Found");
		return header;
	}
	else if(userInfo->httpAnswer == 304){
		header = Concat(header, "Not Modified");
		return header;
	}

	header = Concat(header,"OK");
	header = Concat(header, "\r\nContent-type: ");
	header = Concat(header, mimeType);
	header = Concat(header, "\r\n\n");
	return header;
}

int saveUserInfo(userInfoT userInfo, string userFile){
	
	FILE *fp;
	string info, temp;
	fp = fopen(userFile,"a");
	if(fp == NULL){
		printf("Error in opening file: savefile\n");
		return RET_FAILED;
	}

	info = Concat(userInfo->userIP, " ");
	info = Concat(info, Concat(userInfo->timeStr, "\n\""));
	info = Concat(info, Concat(userInfo->cmdHTTP, " "));
	info = Concat(info, Concat(userInfo->filenameHTTP, " "));
	info = Concat(info, Concat(userInfo->protocolHTTP,"\" "));
	temp = IntegerToString(userInfo->httpAnswer);
	info = Concat(info, Concat(temp, " "));
	temp = IntegerToString(userInfo->bitsDownloaded);
	info = Concat(info, temp);
	
	if(fputs(info,fp) != EOF);
	else{
		printf("Error in writing to the file: savefile\n");
		fclose(fp);
		return RET_FAILED;
	}
	fputs("\n-------------------------------------------------------\n",fp);

	fclose(fp);
	return 1;
}

int checkFileExist(userInfoT userInfo, configT configFile){
	FILE *fp;

	if((fp = fopen(Concat(configFile.filePath,userInfo->filenameHTTP), "r")) == NULL){
		//printf("Could't open file %s\n", userInfo->filenameHTTP);
		return RET_FAILED;
	}
	fclose(fp);
	return RET_SUCCESS;

}

string contentType(string filename){
	string mime = SubString(filename, strlen(filename)-4, strlen(filename));

        if(strcmp(mime, ".jpg") == 0)
                return "image/jpeg";
        else if(strcmp(mime, "jpeg") == 0)
                return "image/jpeg";
        else if(strcmp(mime, "html") == 0 || strcmp(mime, ".htm") == 0)
                return "text/html";
        else if(strcmp(mime, ".png") == 0)
                return "image/png";
        else if(strcmp(mime, ".gif") == 0)
                return "image/gif";
        else if(strcmp(mime, ".zip") == 0)
                return "application/x-zip-compressed";
        else if(strcmp(mime, ".ico") == 0)
                return "image/ico";
        else return "application/octet-stream";
}

int monthToMonth(char *month){

	if(strcmp(month, "Jan") == 0)
		return 0;
	else if(strcmp(month, "Feb") == 0)
		return 1;
	else if(strcmp(month, "Mar") == 0)
		return 2;
	else if(strcmp(month, "Apr") == 0)
		return 3;
	else if(strcmp(month, "May") == 0)
		return 4;
	else if(strcmp(month, "Jun") == 0)
		return 5;
	else if(strcmp(month, "Jul") == 0)
		return 6;
	else if(strcmp(month, "Aug") == 0)
		return 7;
	else if(strcmp(month, "Sep") == 0)
		return 8;
	else if(strcmp(month, "Oct") == 0)
		return 9;
	else if(strcmp(month, "Nov") == 0)
		return 10;
	else if(strcmp(month, "Dec") == 0)
		return 11;
	else
		return -1;
}

int getTime(userInfoT userInfo){

	time_t gTime, lTime, bTime;
	struct tm *gTimeS, *lTimeS;
	double result;
	string temp;
	time(&bTime);
	strftime(userInfo->timeStr, 80, "[ %d/%b/%Y:%H:%M:%S ", localtime( &bTime));

	time(&gTime);
	gTimeS = gmtime(&gTime);
	if((gTime = mktime(gTimeS)) == (time_t)-1){
		strcat(userInfo->timeStr, "-1]");
		return RET_FAILED;
	}
	
	
	time(&lTime);
	lTimeS = localtime(&lTime);
	if((lTime = mktime(lTimeS)) == (time_t)-1){
		strcat(userInfo->timeStr, "-1]");
		return RET_FAILED;
	}

	result = difftime(lTime,gTime);
	temp = IntegerToString((int)((result/60)/60));
	strcat(userInfo->timeStr, temp);
	strcat(userInfo->timeStr, "]");

	return RET_SUCCESS;
	
}